/* 
 * Copyright (C) 2012 Mark Thompson (mark.artifice@gmail.com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.artificetech.videolibrary;

import java.util.Comparator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * A ThreadPoolExecutor for background tasks, backed by a priority queue.
 * 
 * We attempt to run tasks in order of decreasing priority, which is obtained from
 * the Prioritizable interface (if the task run supports it), or DEFAULT_PRIORITY
 * (if it doesn't.) 
 * 
 * Priorities cannot change once a job has been inserted; remove and readd
 * it in that case.
 * 
 * Note that the implementation is very simplistic, problems such as starvation are
 * not dealt with here. For use in this program, the thread is expected to remain idle
 * much of the time.
 */
public class PrioritizedThreadPoolExecutor extends ThreadPoolExecutor {
	public interface Prioritizable {
		public int getPriority();
	}
	
	private static final long KEEPALIVE_TIME=60;
	private static final int DEFAULT_PRIORITY=0;
	
	public PrioritizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize,	ThreadFactory threadFactory) {
		super(corePoolSize, maximumPoolSize, KEEPALIVE_TIME, TimeUnit.SECONDS, createQueue(), threadFactory);
	}

	public PrioritizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize,	int priority) {
		super(corePoolSize, maximumPoolSize, KEEPALIVE_TIME, TimeUnit.SECONDS, createQueue(), prioritizedThreadFactory(priority));
	}
	
	public PrioritizedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) {
		super(corePoolSize, maximumPoolSize, KEEPALIVE_TIME, TimeUnit.SECONDS, createQueue());
	}

	private static BlockingQueue<Runnable> createQueue() {
		Comparator<Runnable> taskComparator=new Comparator<Runnable>() {
			public int compare(Runnable lhs, Runnable rhs) {
				return ((PrioritizedTask<?>)rhs).getPriority() - ((PrioritizedTask<?>)lhs).getPriority();
			}
		};
		
		return new PriorityBlockingQueue<Runnable>(11, taskComparator);
	}
	
	private static class PrioritizedTask<T> extends FutureTask<T> implements Prioritizable {
		Prioritizable task;
		int priority=DEFAULT_PRIORITY;
		
		public PrioritizedTask(Callable<T> callable) {
			super(callable);
			if (callable instanceof Prioritizable) {
				task=(Prioritizable)callable;
				priority=task.getPriority();
			}
		}

		public PrioritizedTask(Runnable runnable, T result) {
			super(runnable, result);
			if (runnable instanceof Prioritizable) { 		
				task=(Prioritizable)runnable;
				priority=task.getPriority();
			}
		}

		public int getPriority() {
			return priority;
		}
		
		// TESTING
		public String toString() { return "p: "+priority+": "+task.toString(); }
	}
	
	private static ThreadFactory prioritizedThreadFactory(final int priority) {
		return new ThreadFactory() {
			public Thread newThread(Runnable r) {
				Thread t=Executors.defaultThreadFactory().newThread(r);
				t.setPriority(priority);
				return t;
			}
		};
	}

	protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
		return new PrioritizedTask<T>(callable);
	}

	protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
		return new PrioritizedTask<T>(runnable, value);
	}
}
